asyncronous vs. syncronous: syncronous is when one thing finishes before another while asyncronous can have a task start, then immediately move on whether that initial task finished or not.
async + await just pauses for certain info without blocking like an alert would do.
note: async + await is built on top of promises, it is not something seperate.
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
async function go() {
console.log('start...');
await breath(1000);
console.log('end')
}
go();
In console you will see output of 'start...' followed by 'end' after what seems to be a brief pause, which is the setTimeout of 1sec.
Note 1: await
cannot be used top-level, you'd get an error in console, such as SyntaxError: await is only valid in async functions and async generators
Note 2: No callback nightmare, no chaining of .then()
s, no scoping introduced (all one level).
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
async function go() {
console.log('start...');
const res = await breath(1000);
console.log(res);
const res2 = await breath(5000);
console.log(res2);
const res3 = await breath(500);
console.log(res3);
console.log('end')
}
go();
capture value function returns^ displaying amount it took to be done, below is example console output of values resolved from promise function^
start...
1000 ms done
5000 ms done
500 ms done
end
if await
was to be removed it would return a promise, but having await
in front of the promise breath will return promise and put out a sort of notice to hold on for a few seconds to wait for (in this case) breath function to resolve or reject.
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
async function go() {
try {
console.log('start...');
const res = await breath(1000);
console.log(res);
const res2 = await breath(5000);
console.log(res2);
const res3 = await breath(300);
console.log(res3);
console.log('end');
} catch(err) {
console.error(Error(`oh snap: ${err}`));
}
}
go();
^which will run as before, but w/ an amount to changed to < 500, we catch
the error.
high order function route: what is a high order function? a function that takes in a function as an argument and returns a new function
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
// fn is any function, such as something like -> .map(function(){ })
function catchErrors(fn) {
return function() {
return fn().catch((err) => {
console.error(Error(`whoooa -> ${err}`))
});
}
}
async function go() {
console.log('start...');
const res = await breath(1000);
console.log(res);
const res2 = await breath(5000);
console.log(res2);
const res3 = await breath(300);
console.log(res3);
console.log('end');
}
// go(); instead of this
const wrappedFn = catchErrors(go);
wrappedFn();
a high order function would stream lines catching multiple errors by wrapping into another function that would act as a catch all
breaking it down:
a function catchErrors
that returns another function that then returns a function that runs with a catch tacked on the end.
running wrappedFn();
is not running go
, instead we've wrapped go
in catchErrors
that outputs brand new function wrappedFn
what about if go
took an argument?
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
// fn is any function, such as something like -> .map(function(){ })
function catchErrors(fn) {
return function() {
return fn().catch((err) => {
console.error(Error(`whoooa -> ${err}`))
});
}
}
async function go(name) {
console.log(`${name}, starting...`);
const res = await breath(1000);
console.log(res);
const res2 = await breath(5000);
console.log(res2);
const res3 = await breath(300);
console.log(res3);
console.log('end');
}
// go(); instead of this
const wrappedFn = catchErrors(go);
wrappedFn('jo');
as is^ will not work, name will be undefined. Although, putting name
as argument in the returns functions in catchErrors
would output correctly it is not an efficient/ flexible option. Instead, using ...rest
and ...spread
would keep it flexible as it would take whatever amount of arguments one would decide to use
function catchErrors(fn) {
return function(...args) {
return fn(...args).catch((err) => {
console.error(Error(`whoooa -> ${err}`))
});
}
}
using rest ...args
in first returned function captures all arguments in an array. Then, with spread ...args
in second return to spread all arguments into that function.
put together (with added argument to show flexibility):
function breath(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('too small of a value');
}
setTimeout(() => resolve(`${amount} ms done`), amount);
})
}
// fn is any function, such as something like -> .map(function(){ })
function catchErrors(fn) {
return function(...args) {
return fn(...args).catch((err) => {
console.error(Error(`whoooa -> ${err}`))
});
}
}
async function go(name1, name2, name3) {
console.log(`Hey, ${name1}, ${name2}, ${name3}! It's starting...`);
const res = await breath(1000);
console.log(res);
const res2 = await breath(5000);
console.log(res2);
const res3 = await breath(300);
console.log(res3);
console.log('end');
}
// go(); instead of this
const wrappedFn = catchErrors(go);
wrappedFn('jo', 'enigma', 'scrappy');
1 arg, 3 or whatever... doesn't matter. The rest and spread adjust to the number of arg without having to adjust the catchErrors function with those same number of args and THAT... is pretty damn awesome :D
Note: as snazzy as this was it shouldn't be something used ALL the time because there are cases where you would want to catch the errors inside your function to specifically handle it a certain way.
ex: catching a form validation error would be handled differently than when encountering another type of error.
above snippet/example is good for use case where you want to handle all errors in similar fashion w/o multiple try/catch errors.
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos');
const p2 = fetch('https://api.github.com/users/stolinski');
}
go();
we wont store the return results, but the promises (p1 + p2)
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos');
const p2 = fetch('https://api.github.com/users/stolinski');
const res = await Promise.all([p1, p2]);
console.log(res);
}
go();
Promise.all()
combines both promises into one, not resolving itself until both come back.
Note: don't forget to add await
in front of Promise.all
, if no await
console output will be Promise { <state>: "pending" }
what will return is not the end user data, it will look something like this
0: Response { type: "cors", url: "https://api.github.com/users/wesbos", redirected: false, … }
1: Response { type: "cors", url: "https://api.github.com/users/stolinski", redirected: false, … }
length: 2
Note: using fetch requires a kind of 2-step process; first fetch data, then convert to JSON which returns another promise.
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos').then(res => res.json());
const p2 = fetch('https://api.github.com/users/stolinski').then(res => res.json());
const res = await Promise.all([p1, p2]);
console.log(res);
}
go();
adding the then()
to convert each response into json returns an array of data, such as:
0: Object { login: "wesbos", id: 176013, avatar_url: "https://avatars2.githubusercontent.com/u/176013?v=4", … }
1: Object { login: "stolinski", id: 669383, avatar_url: "https://avatars1.githubusercontent.com/u/669383?v=4", … }
length: 2
another way to convert to JSON, instead of attaching then()
2x...
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos');
const p2 = fetch('https://api.github.com/users/stolinski');
const res = await Promise.all([p1, p2]);
const packagedPromises = res.map(r => r.json());
const packagedData = await Promise.all(packagedPromises);
console.log(packagedData);
}
go();
...is to package the promises (packagePromises
) put into res
variable by mapping over them and then do another Promise.all()
that will store all json data into packagedData
variable. Then, packagedData
should output same array of data spit out in previous example when attaching then()
to each fetch.
to seperate data output, destructure!
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos');
const p2 = fetch('https://api.github.com/users/stolinski');
const res = await Promise.all([p1, p2]);
const packagedPromises = res.map(r => r.json());
const [person1, person2] = await Promise.all(packagedPromises);
console.log(person1, person2);
}
go();
the difference:
(2) […]
0: Object { login: "wesbos", id: 176013, avatar_url: "https://avatars2.githubusercontent.com/u/176013?v=4", … }
1: Object { login: "stolinski", id: 669383, avatar_url: "https://avatars1.githubusercontent.com/u/669383?v=4", … }
length: 2
<prototype>: Array []
no destructure^
vs.
Object { login: "wesbos", id: 176013, avatar_url: "https://avatars2.githubusercontent.com/u/176013?v=4", gravatar_id: "", url: "https://api.github.com/users/wesbos", html_url: "https://github.com/wesbos", followers_url: "https://api.github.com/users/wesbos/followers", following_url: "https://api.github.com/users/wesbos/following{/other_user}", gists_url: "https://api.github.com/users/wesbos/gists{/gist_id}", starred_url: "https://api.github.com/users/wesbos/starred{/owner}{/repo}", … }
Object { login: "stolinski", id: 669383, avatar_url: "https://avatars1.githubusercontent.com/u/669383?v=4", gravatar_id: "", url: "https://api.github.com/users/stolinski", html_url: "https://github.com/stolinski", followers_url: "https://api.github.com/users/stolinski/followers", following_url: "https://api.github.com/users/stolinski/following{/other_user}", gists_url: "https://api.github.com/users/stolinski/gists{/gist_id}", starred_url: "https://api.github.com/users/stolinski/starred{/owner}{/repo}", … }
destructure^
the destructure option comes in handy when doing ajax requests
async function snagDeets(names) {
const promises = names.map(name => fetch(`https://api.github.com/users/${name}`).then(res => res.json()));
const [deets1, deets2] = await Promise.all(promises);
console.log(deets1, deets2);
}
snagDeets(['wesbos', 'stolinski']);
console output gist:
Object { login: "wesbos", id: 176013, avatar_url: "https://avatars2.githubusercontent.com/u/176013?v=4", gravatar_id: "", url: "https://api.github.com/users/wesbos", html_url: "https://github.com/wesbos", followers_url: "https://api.github.com/users/wesbos/followers", following_url: "https://api.github.com/users/wesbos/following{/other_user}", gists_url: "https://api.github.com/users/wesbos/gists{/gist_id}", starred_url: "https://api.github.com/users/wesbos/starred{/owner}{/repo}", … }
Object { login: "stolinski", id: 669383, avatar_url: "https://avatars1.githubusercontent.com/u/669383?v=4", gravatar_id: "", url: "https://api.github.com/users/stolinski", html_url: "https://github.com/stolinski", followers_url: "https://api.github.com/users/stolinski/followers", following_url: "https://api.github.com/users/stolinski/following{/other_user}", gists_url: "https://api.github.com/users/stolinski/gists{/gist_id}", starred_url: "https://api.github.com/users/stolinski/starred{/owner}{/repo}", … }
or leave it as array if number of args being passed may not be a set number
async function snagDeets(names) {
const promises = names.map(name => fetch(`https://api.github.com/users/${name}`).then(res => res.json()));
const deets = await Promise.all(promises);
console.log(deets);
}
snagDeets(['wesbos', 'stolinski', 'freecodecamp']);
console output gist:
[…]
0: Object { login: "wesbos", id: 176013, avatar_url: "https://avatars2.githubusercontent.com/u/176013?v=4", … }
1: Object { login: "stolinski", id: 669383, avatar_url: "https://avatars1.githubusercontent.com/u/669383?v=4", … }
2: Object { login: "freeCodeCamp", id: 9892522, avatar_url: "https://avatars0.githubusercontent.com/u/9892522?v=4", … }
length: 3
<prototype>: Array []
looking in network tab shows 3 GET requests
Note: a lot can be done with async/await/Promise.all in conjuction with map, reduce and all those snazzy methods
Random note that may or may not be useful out in the world-- Promise.race()
-- multi-requests, resolving upon return of fastest promise from array of promises that come back.
Reality: still alot of callback based JS, like native browser APIs/ existing libraries so using async/await/promises may not always be possible.
Some libraries are changing where they both return promises/accept callback for older legacy code, but if in only callback land and want to use promises... in comes 'promisifying' functions. Note: Although there are libraries out in the world that will do this for you, DIY is pretty simple.
ex: older type API, uses callback based function
navigator.geolocation.getCurrentPosition(function(pos) {
console.log('it worked!');
console.log(pos);
}, function(err) {
console.log('it failed');
console.log(err);
});
ex: promisify
function getCurrentPosition() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
})
}
async function go() {
console.log('starting...');
const position = await getCurrentPosition();
console.log(position);
console.log('finished');
}
go();
console.log(s) of starting/finished shows await in action^
taking arguments, ex: accuracy depending on battery life user has
function getCurrentPosition(...args) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(...args, resolve, reject);
})
}
async function go() {
console.log('starting...');
// now if await getCurrentPosition() was to get passed options object, spread/rest will take care of those
const position = await getCurrentPosition();
console.log(position);
console.log('finished');
}
go();
promisifying w/o library^
to use library, check out es6-promisify or if using node there's a built in utility on the util package that comes baked in w/ Node.